using System;
using System.Threading;

namespace LiteNetLib
{
	internal sealed class ReliableChannel : BaseChannel
	{
		private sealed class PendingPacket
		{
			public NetPacket Packet;

			public long TimeStamp;

			public bool Sended;

			public override string ToString()
			{
				if (Packet != null)
				{
					return Packet.Sequence.ToString();
				}
				return "Empty";
			}
		}

		private readonly NetPacket _outgoingAcks;

		private readonly PendingPacket[] _pendingPackets;

		private readonly NetPacket[] _receivedPackets;

		private readonly bool[] _earlyReceived;

		private int _localSeqence;

		private int _remoteSequence;

		private int _localWindowStart;

		private int _remoteWindowStart;

		private bool _mustSendAcks;

		private readonly bool _ordered;

		private readonly int _windowSize;

		private const int BitsInByte = 8;

		public ReliableChannel(NetPeer peer, bool ordered)
			: base(peer)
		{
			_windowSize = 64;
			_ordered = ordered;
			_pendingPackets = new PendingPacket[_windowSize];
			for (int i = 0; i < _pendingPackets.Length; i++)
			{
				_pendingPackets[i] = new PendingPacket();
			}
			if (_ordered)
			{
				_receivedPackets = new NetPacket[_windowSize];
			}
			else
			{
				_earlyReceived = new bool[_windowSize];
			}
			_localWindowStart = 0;
			_localSeqence = 0;
			_remoteSequence = 0;
			_remoteWindowStart = 0;
			int size = (_windowSize - 1) / 8 + 1;
			_outgoingAcks = new NetPacket(_ordered ? PacketProperty.AckReliableOrdered : PacketProperty.AckReliable, size);
		}

		public void ProcessAck(NetPacket packet)
		{
			if (packet.Size != _outgoingAcks.Size)
			{
				return;
			}
			ushort sequence = packet.Sequence;
			int num = NetUtils.RelativeSequenceNumber(_localWindowStart, sequence);
			if (sequence >= 32768 || num < 0 || num >= _windowSize)
			{
				return;
			}
			byte[] rawData = packet.RawData;
			Monitor.Enter(_pendingPackets);
			int num2 = _localWindowStart;
			while (num2 != _localSeqence && NetUtils.RelativeSequenceNumber(num2, sequence) < _windowSize)
			{
				int num3 = num2 % _windowSize;
				int num4 = 3 + num3 / 8;
				int num5 = num3 % 8;
				if ((rawData[num4] & (1 << num5)) != 0)
				{
					if (num2 == _localWindowStart)
					{
						_localWindowStart = (_localWindowStart + 1) % 32768;
					}
					PendingPacket pendingPacket = _pendingPackets[num3];
					if (pendingPacket.Packet != null)
					{
						Peer.Recycle(pendingPacket.Packet);
						pendingPacket.Packet = null;
					}
				}
				num2 = (num2 + 1) % 32768;
			}
			Monitor.Exit(_pendingPackets);
		}

		public override void SendNextPackets()
		{
			if (_mustSendAcks)
			{
				_mustSendAcks = false;
				Monitor.Enter(_outgoingAcks);
				Peer.SendUserData(_outgoingAcks);
				Monitor.Exit(_outgoingAcks);
			}
			long ticks = DateTime.UtcNow.Ticks;
			Monitor.Enter(_pendingPackets);
			Monitor.Enter(OutgoingQueue);
			while (OutgoingQueue.Count > 0 && NetUtils.RelativeSequenceNumber(_localSeqence, _localWindowStart) < _windowSize)
			{
				PendingPacket obj = _pendingPackets[_localSeqence % _windowSize];
				obj.Sended = false;
				obj.Packet = OutgoingQueue.Dequeue();
				obj.Packet.Sequence = (ushort)_localSeqence;
				_localSeqence = (_localSeqence + 1) % 32768;
			}
			Monitor.Exit(OutgoingQueue);
			double resendDelay = Peer.ResendDelay;
			for (int num = _localWindowStart; num != _localSeqence; num = (num + 1) % 32768)
			{
				PendingPacket pendingPacket = _pendingPackets[num % _windowSize];
				if (pendingPacket.Packet != null && (!pendingPacket.Sended || !((double)(ticks - pendingPacket.TimeStamp) < resendDelay * 10000.0)))
				{
					pendingPacket.TimeStamp = ticks;
					pendingPacket.Sended = true;
					Peer.SendUserData(pendingPacket.Packet);
				}
			}
			Monitor.Exit(_pendingPackets);
		}

		public override void ProcessPacket(NetPacket packet)
		{
			int sequence = packet.Sequence;
			if (sequence >= 32768)
			{
				return;
			}
			int num = NetUtils.RelativeSequenceNumber(sequence, _remoteWindowStart);
			if (NetUtils.RelativeSequenceNumber(sequence, _remoteSequence) > _windowSize || num < 0 || num >= _windowSize * 2)
			{
				return;
			}
			Monitor.Enter(_outgoingAcks);
			int num3;
			int num4;
			int num5;
			if (num >= _windowSize)
			{
				int num2 = (_remoteWindowStart + num - _windowSize + 1) % 32768;
				_outgoingAcks.Sequence = (ushort)num2;
				while (_remoteWindowStart != num2)
				{
					num3 = _remoteWindowStart % _windowSize;
					num4 = 3 + num3 / 8;
					num5 = num3 % 8;
					_outgoingAcks.RawData[num4] &= (byte)(~(1 << num5));
					_remoteWindowStart = (_remoteWindowStart + 1) % 32768;
				}
			}
			_mustSendAcks = true;
			num3 = sequence % _windowSize;
			num4 = 3 + num3 / 8;
			num5 = num3 % 8;
			if ((_outgoingAcks.RawData[num4] & (1 << num5)) != 0)
			{
				Monitor.Exit(_outgoingAcks);
				return;
			}
			_outgoingAcks.RawData[num4] |= (byte)(1 << num5);
			Monitor.Exit(_outgoingAcks);
			if (sequence == _remoteSequence)
			{
				Peer.AddIncomingPacket(packet);
				_remoteSequence = (_remoteSequence + 1) % 32768;
				if (_ordered)
				{
					NetPacket p;
					while ((p = _receivedPackets[_remoteSequence % _windowSize]) != null)
					{
						_receivedPackets[_remoteSequence % _windowSize] = null;
						Peer.AddIncomingPacket(p);
						_remoteSequence = (_remoteSequence + 1) % 32768;
					}
				}
				else
				{
					while (_earlyReceived[_remoteSequence % _windowSize])
					{
						_earlyReceived[_remoteSequence % _windowSize] = false;
						_remoteSequence = (_remoteSequence + 1) % 32768;
					}
				}
			}
			else if (_ordered)
			{
				_receivedPackets[num3] = packet;
			}
			else
			{
				_earlyReceived[num3] = true;
				Peer.AddIncomingPacket(packet);
			}
		}
	}
}
